/**
 * Filter by a configurable Ext.picker.DatePicker menu
 * 
 * This filter allows for the following configurations:
 *  - Any of the normal configs will be passed through to either component. -
 * There can be a docked config. - The timepicker can be on the right or left
 * (datepicker, too, of course). - Choose which component will initiate the
 * filtering, i.e., the event can be configured to be bound to either the
 * datepicker or the timepicker, or if there is a docked config it be
 * automatically have the handler bound to it.
 * 
 * Although not shown here, this class accepts all configuration options for
 * {@link Ext.picker.Date} and {@link Ext.picker.Time}.
 * 
 * In the case that a custom dockedItems config is passed in, the class will
 * handle binding the default listener to it so the developer need not worry
 * about having to do it.
 * 
 * The default dockedItems position and the toolbar's button text can be passed
 * a config for convenience, i.e.,:
 * 
 * dock: { buttonText: 'Click to Filter', dock: 'left' }
 * 
 * Or, pass in a full dockedItems config:
 * 
 * dock: { dockedItems: { xtype: 'toolbar', dock: 'bottom', ... } }
 * 
 * Or, give a value of `true` to accept dock defaults:
 * 
 * dock: true
 * 
 * But, it must be one or the other.
 * 
 * Example Usage:
 * 
 * var filters = Ext.create('Ext.ux.grid.GridFilters', { ... filters: [{ //
 * required configs type: 'datetime', dataIndex: 'date',
 *  // optional configs positionDatepickerFirst: false, //selectDateToFilter:
 * false, // this is overridden b/c of the presence of the dock cfg object
 * 
 * date: { format: 'm/d/Y', },
 * 
 * time: { format: 'H:i:s A', increment: 1 },
 * 
 * dock: { buttonText: 'Click to Filter', dock: 'left'
 *  // allows for custom dockedItems cfg //dockedItems: {} } }] });
 * 
 * In the above example, note that the filter is being passed a {@link #date}
 * config object, a {@link #time} config object and a {@link #dock} config.
 * These are all optional.
 * 
 * As for positioning, the datepicker will be on the right, the timepicker on
 * the left and the docked items will be docked on the left. In addition, since
 * there's a {@link #dock} config, clicking the button in the dock will trigger
 * the filtering.
 */
Ext.define('Ext.ux.grid.filter.DateTimeFilter', {
	extend : 'Ext.ux.grid.filter.DateFilter',
	alias : 'gridfilter.datetime',

	/**
	 * @private
	 */
	dateDefaults : {
		xtype : 'datepicker',
		format : 'm/d/Y'
	},

	/**
	 * @private
	 */
	timeDefaults : {
		xtype : 'timepicker',
		width : 100,
		height : 200,
		format : 'g:i A'
	},

	/**
	 * @private
	 */
	dockDefaults : {
		dock : 'top',
		buttonText : 'Filter'
	},

	/**
	 * @cfg {Object} date A {@link Ext.picker.Date} can be configured here. Uses
	 *      {@link #dateDefaults} by default.
	 */

	/**
	 * @cfg {Object} time A {@link Ext.picker.Time} can be configured here. Uses
	 *      {@link #timeDefaults} by default.
	 */

	/**
	 * @cfg {Boolean/Object} dock A
	 *      {@link Ext.panel.AbstractPanel#cfg-dockedItems} can be configured
	 *      here. A `true` value will use the {@link #dockDefaults} default
	 *      configuration. If present, the button in the docked items will
	 *      initiate the filtering.
	 */

	/**
	 * @cfg {Boolean} [selectDateToFilter=true] By default, the datepicker has
	 *      the default event listener bound to it. Setting to `false` will bind
	 *      it to the timepicker.
	 * 
	 * The config will be ignored if there is a `dock` config.
	 */
	selectDateToFilter : true,

	/**
	 * @cfg {Boolean} [positionDatepickerFirst=true] Positions the datepicker
	 *      within its container. A `true` value will place it on the left in
	 *      the container. Set to `false` if the timepicker should be placed on
	 *      the left. Defaults to `true`.
	 */
	positionDatepickerFirst : true,

	reTime : /\s(am|pm)/i,
	reItemId : /\w*-(\w*)$/,

	/**
	 * Replaces the selected value of the timepicker with the default 00:00:00.
	 * 
	 * @private
	 * @param {Object}
	 *            date
	 * @param {Ext.picker.Time}
	 *            timepicker
	 * @return Date object
	 */
	addTimeSelection : function(date, timepicker) {
		var me = this, selection = timepicker.getSelectionModel()
				.getSelection(), time, len, fn, val, i = 0, arr = [], timeFns = [
				'setHours', 'setMinutes', 'setSeconds', 'setMilliseconds'];

		if (selection.length) {
			time = selection[0].get('disp');

			// Loop through all of the splits and add the time values.
			arr = time.replace(me.reTime, '').split(':');

			for (len = arr.length; i < len; i++) {
				fn = timeFns[i];
				val = arr[i];

				if (val) {
					date[fn](parseInt(val, 10));
				}
			}
		}

		return date;
	},

	/**
	 * @private Template method that is to initialize the filter and install
	 *          required menu items.
	 */
	init : function(config) {
		var me = this, dateCfg = Ext.applyIf(me.date || {}, me.dateDefaults), timeCfg = Ext
				.applyIf(me.time || {}, me.timeDefaults), dockCfg = me.dock, // should
																				// not
																				// default
																				// to
																				// empty
																				// object
		defaultListeners = {
			click : {
				scope : me,
				click : me.onMenuSelect
			},
			select : {
				scope : me,
				select : me.onMenuSelect
			}
		}, pickerCtnCfg, i, len, item, cfg, items = [dateCfg, timeCfg],

		// we need to know the datepicker's position in the items array
		// for when the itemId name is bound to it before adding to the menu
		datepickerPosition = 0;

		if (!me.positionDatepickerFirst) {
			items = items.reverse();
			datepickerPosition = 1;
		}

		pickerCtnCfg = Ext.apply(me.pickerOpts, {
					xtype : !dockCfg ? 'container' : 'panel',
					layout : 'hbox',
					items : items
				});

		// If there's no dock config then bind the default listener to the
		// desired picker.
		if (!dockCfg) {
			if (me.selectDateToFilter) {
				dateCfg.listeners = defaultListeners.select;
			} else {
				timeCfg.listeners = defaultListeners.select;
			}
		} else if (dockCfg) {
			me.selectDateToFilter = null;

			if (dockCfg.dockedItems) {
				pickerCtnCfg.dockedItems = dockCfg.dockedItems;
				// right now, it's using the first item
				pickerCtnCfg.dockedItems.items[dockCfg.bindToItem || 0].listeners = defaultListeners.click;
			} else {
				// dockCfg can be `true` if button text and dock position
				// defaults are wanted
				if (Ext.isBoolean(dockCfg)) {
					dockCfg = {};
				}
				dockCfg = Ext.applyIf(dockCfg, me.dockDefaults);
				pickerCtnCfg.dockedItems = {
					xtype : 'toolbar',
					dock : dockCfg.dock,
					items : [{
								xtype : 'button',
								text : dockCfg.buttonText,
								flex : 1,
								listeners : defaultListeners.click
							}]
				};
			}
		}

		me.fields = {};
		for (i = 0, len = me.menuItems.length; i < len; i++) {
			item = me.menuItems[i];
			if (item !== '-') {
				pickerCtnCfg.items[datepickerPosition].itemId = item;

				cfg = {
					itemId : 'range-' + item,
					text : me[item + 'Text'],
					menu : Ext.create('Ext.menu.Menu', {
								items : pickerCtnCfg
							}),
					listeners : {
						scope : me,
						checkchange : me.onCheckChange
					}
				};
				item = me.fields[item] = Ext.create('Ext.menu.CheckItem', cfg);
			}
			me.menu.add(item);
		}
		me.values = {};
	},

	/**
	 * @private
	 */
	onCheckChange : function(item, checked) {
		var me = this, menu = item.menu, timepicker = menu.down('timepicker'), datepicker = menu
				.down('datepicker'), itemId = datepicker.itemId, values = me.values;

		if (checked) {
			values[itemId] = me.addTimeSelection(datepicker.value, timepicker);
		} else {
			delete values[itemId];
		}
		me.setActive(me.isActivatable());
		me.fireEvent('update', me);
	},

	/**
	 * Handler for when the DatePicker for a field fires the 'select' event
	 * 
	 * @param {Ext.picker.Date}
	 *            picker
	 * @param {Object}
	 *            date
	 */
	onMenuSelect : function(picker, date) {
		// NOTE: we need to redefine the picker.
		var me = this, menu = me.menu, checkItemId = menu.getFocusEl().itemId
				.replace(me.reItemId, '$1'), fields = me.fields, field;

		picker = menu.queryById(checkItemId);
		field = me.fields[picker.itemId];
		field.setChecked(true);

		if (field == fields.on) {
			fields.before.setChecked(false, true);
			fields.after.setChecked(false, true);
		} else {
			fields.on.setChecked(false, true);
			if (field == fields.after && me.getFieldValue('before') < date) {
				fields.before.setChecked(false, true);
			} else if (field == fields.before
					&& me.getFieldValue('after') > date) {
				fields.after.setChecked(false, true);
			}
		}
		me.fireEvent('update', me);

		// The timepicker's getBubbleTarget() returns the boundlist's
		// implementation,
		// so it doesn't look up ownerCt chain (it looks up this.pickerField).
		// This is a problem :)
		// This can be fixed by just walking up the ownerCt chain
		// (same thing, but confusing without comment).
		picker.ownerCt.ownerCt.hide();
	},

	/**
	 * @private Template method that is to get and return serialized filter data
	 *          for transmission to the server.
	 * @return {Object/Array} An object or collection of objects containing key
	 *         value pairs representing the current configuration of the filter.
	 */
	getSerialArgs : function() {
		var me = this, key, fields = me.fields, args = [];

		for (key in fields) {
			if (fields[key].checked) {
				args.push({
					type : 'datetime',
					comparison : me.compareMap[key],
					value : Ext.Date
							.format(
									me.getFieldValue(key),
									(me.date.format || me.dateDefaults.format)
											+ ' '
											+ (me.time.format || me.timeDefaults.format))
				});
			}
		}
		return args;
	},

	/**
	 * @private Template method that is to set the value of the filter.
	 * @param {Object}
	 *            value The value to set the filter
	 * @param {Boolean}
	 *            preserve true to preserve the checked status of the other
	 *            fields. Defaults to false, unchecking the other fields
	 */
	setValue : function(value, preserve) {
		var me = this, fields = me.fields, key, val, datepicker;

		for (key in fields) {
			val = value[key];
			if (val) {
				datepicker = me.menu.down('datepicker[itemId="' + key + '"]');
				// Note that calling the Ext.picker.Date:setValue() calls
				// Ext.Date.clearTime(),
				// which we don't want, so just call update() instead and set
				// the value on the component.
				datepicker.update(val);
				datepicker.value = val;

				fields[key].setChecked(true);
			} else if (!preserve) {
				fields[key].setChecked(false);
			}
		}
		me.fireEvent('update', me);
	},

	/**
	 * Template method that is to validate the provided Ext.data.Record against
	 * the filters configuration.
	 * 
	 * @param {Ext.data.Record}
	 *            record The record to validate
	 * @return {Boolean} true if the record is valid within the bounds of the
	 *         filter, false otherwise.
	 */
	validateRecord : function(record) {
		// remove calls to Ext.Date.clearTime
		var me = this, key, pickerValue, val = record.get(me.dataIndex);

		if (!Ext.isDate(val)) {
			return false;
		}

		val = val.getTime();

		for (key in me.fields) {
			if (me.fields[key].checked) {
				pickerValue = me.getFieldValue(key).getTime();
				if (key == 'before' && pickerValue <= val) {
					return false;
				}
				if (key == 'after' && pickerValue >= val) {
					return false;
				}
				if (key == 'on' && pickerValue != val) {
					return false;
				}
			}
		}
		return true;
	}
});
